import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeoutException;


public class server {

	/**
	 * @param args
	 */
	public static ServerState state;
	public static MulticastDB db;
	public static boolean signalToKillAllServerThreads = false;
	public static String[][] ServerConfigInfo = null;
	
	public static void main(String[] args) 
	{
		//***setup state***
		state = ServerState.setup;
		
    	db = new MulticastDB("db.csv");
    	
    	BufferedReader stdIn = new BufferedReader(
                new InputStreamReader(System.in)); //get reference to terminal input
    	
    	ConfigFileReader config = new ConfigFileReader("config.ini");
    	try {
			config.ParseFile();
		} catch (NumberFormatException e) {
			
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			
			e.printStackTrace();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
		
		if(config.rootserver == null)
		{
			System.err.println("ERROR: root server not specified in " + config.filename);
			System.err.println("  Please add a line to config.ini like the following ex:");
			System.err.println("  msd1	128.128.224.10	10001	root");
			System.exit(1);
		}
		
		
		//try to start the socket server on that port
			//if unsuccesful in starting on port
				//output error code 0x001 and exit
			//if successful
				//output port listening on to console
		ServerConfigInfo = config.servers;
		int numServers = ServerConfigInfo.length;
		ServerWorker[] swArray = new ServerWorker[numServers];
		Thread[] tArray = new Thread[numServers];
		int index = 0;
		for(String[] serverInfo : ServerConfigInfo)
		{
			swArray[index] = new ServerWorker(serverInfo);
			tArray[index] = new Thread(swArray[index]);
			tArray[index].start();
		}
		//System.out.println("all threads started");
		
		System.out.println("Type \"exit\" and hit enter at any time to close the server.");
		while(true)
		{
			try {
				if(stdIn.ready() && stdIn.readLine().startsWith("exit"))
				{
					signalToKillAllServerThreads = true;
					break;
				}
			} catch (IOException e) {		}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
		}
		
		System.out.println("waiting for all threads to close");
		try {
			Helpers.waitForAllThreadsToClose(tArray, 30);
		} catch (TimeoutException e) {
			System.err.println("ERROR: Timed out waiting for all threads to close.");
			System.exit(1);
		}
		System.out.println("all threads closed. ending process.");
			
		
		
		
		//***ready state***
		//if child:
			//send a HELLO message to its parent with the number of child
			//nodes it has including itself
			//wait for the ADD-SPACE messages to arrive
			//do something with this info to set up own space in memory?
		//if parent:
			//wait for counts to arrive from children?
			//wait for ADD-SPACE messages to arrive
				//forward on to children
				//do something with this info to set up own space in memory?
		//if root:
			//then you are not a child
			//wait for counts to arrive from all children?
			//use a hashing function to divide the key space into n+1 parts
				//must be equally divided among children
			//issue the ADD-SPACE messages to children
				//this contains the space area that each child is in charge of
		
		//case 1: communicating with a search/register tool client
			//sit and listen and wait for a client to connect
			//upon hearing a client, create a thread and
			//in that thread go to *connected state*
		//case 2: msd server <--> msd server
			//if child
				//send periodic HELLO messages to its parent
					//inside thread
			//if parent
				//respond to HELLO messages with a REP-HELLO message
					//must do this inside threads
				//also sends ADD-SPACE messages to its children as specified above
			
			//may send MSD-PROBE messages to each other when the search item
			//that the client is searching for is out of their hash value scope
			//upon receiving an MSD-PROBE
				//if this server has the sought out record
					//send an MSD-PROBE-REPLY to the originating server
					//that sent out the MSD-PROBE message
		
		//***connected state***
		//may receive a
			//CHECK, REGISTER, SEARCH, EXT-SEARCH, INVALIDATE, or BYE message
			//any other message or improperly formed message will provoke an ERROR reply
		//shall respond to:
			//CHECK			CHECK-RESPONSE
			//REGISTER		REGISTER-STATUS
			//SEARCH		SEARCH-RESPONSE or REDIRECT
							//when over, TX-END
			//EXT-SEARCH	EXT-SEARCH-RESPONSE or EXT-SEARCH-INVALID
							//when over, TX-END
			//INVALIDATE	REDIRECT
			//BYE			BYE
							//server shall return to its ready state
		
	}



}

class ServerWorker implements Runnable {
	  public MulticastDB db;
	  private String[] serverInfo;
	  private String serverName;
	  public String ipAddress;
	  public int portNumber;
	  private String parentMSD;
	  public boolean isParent;
	  public boolean isChild;
	  public boolean isRoot;
	  public String[][] children;
	  public String[] parent;
	  private int numAllChildren;
	  private int numImmediateChildren;
	  public String idHash;
	  public String rangeStart;
	  public String rangeEnd;
	  public String myRangeStart;
	  public String myRangeEnd;
	  public String parentRangeStart;
	  public String parentRangeEnd;
	  private String[][] keywordHashBoundaries;
	  public String[][] childKeywordHashBoundaries;
	  public boolean[] countArrivedFromChild;
	  public boolean addSpaceArrivedFromParent;
	  public boolean haveSentAddSpaceMessages;
	  public int[] childCounts;
	  public String[] childrenIDHash;
	  private long helloTimerPeriod = 120000; //in milliseconds
	  private long helloTimerEnd = System.currentTimeMillis() + 1000000; //abs time in milliseconds
	private boolean haveSentCountToParent;
	  

	//Constructor
	  ServerWorker(String[] server) {
	    this.serverInfo = server;
	    if(server != null)
	    {
	    	if(server.length > Helpers.SERVERNAME)
	    		serverName = server[Helpers.SERVERNAME];
	    	if(server.length > Helpers.IPADDRESS)
	    		ipAddress = server[Helpers.IPADDRESS];
	    	if(server.length > Helpers.PORTNUMBER)
	    	{
	    		try{
	    			portNumber = Integer.parseInt(server[Helpers.PORTNUMBER]);}
	    		catch(NumberFormatException e1){
	    			portNumber = -1;
					printErrorToConsole("ERROR: server port number \"" + server[Helpers.PORTNUMBER] + "\"");
					printErrorToConsole("  was not recognized as an integer. Please format correctly.");
					System.exit(1);
	    		}
	    	}
	    	if(server.length > Helpers.PARENTMSD)
	    		parentMSD = server[Helpers.PARENTMSD];
	    	db = new MulticastDB(serverName + "_db.csv");
	    	db.clearDB();
	    }
	  }
	  
	  public void printToConsole(String message)
	  {
		  System.out.println(" " + serverName + ": " + message);
		  System.out.flush();
	  }
	  
	  public void printErrorToConsole(String message)
	  {
		  System.err.println(" " + serverName + ": " + message);
	  }

	  public void run()
	  {
		  if(serverInfo.length < 4)
			{
				printErrorToConsole("ERROR: server not specified correctly in ");
				printErrorToConsole("  Supplied: " + Arrays.deepToString(serverInfo));
				printErrorToConsole("  Expected: <name>	<ip>	<port>	<parent>");
				System.exit(1);
			}
		  
		  
		  isParent = Helpers.isParent(server.ServerConfigInfo, serverInfo);
		  isChild = Helpers.isChild(serverInfo);
		  isRoot = Helpers.isRoot(serverInfo);
		  children = Helpers.getChildren(server.ServerConfigInfo, serverInfo);
		  
		  parent = Helpers.getParent(server.ServerConfigInfo, serverInfo);
		  if(children != null)
			  numImmediateChildren = children.length;
		  else
			  numImmediateChildren = 0;
		  numAllChildren = Helpers.getNumAllChildren(server.ServerConfigInfo, serverInfo);
		  idHash = Helpers.getMD5Hash(serverName);
		  childCounts = new int[numImmediateChildren];
		  childrenIDHash = new String[numImmediateChildren];
		  for(int i = 0; i < numImmediateChildren; i++)
		  {
			  childrenIDHash[i] = Helpers.getMD5Hash(children[i][Helpers.SERVERNAME]);
		  }
		  
		  String addressStartHex32 = null, addressEndHex32 = null;
		
			
			if(portNumber == -1)
			{
				printErrorToConsole("ERROR: server port not specified properly in config file.");
				printErrorToConsole("  Please add a line to config.ini like the following ex:");
				printErrorToConsole("  msd1	128.128.224.10	<port>	<parent>");
				System.exit(1);
			}
			if(portNumber < 1 || portNumber > 65535) // port must be in range 1-65535, preferably > 49151
	        {
	        	printErrorToConsole("ERROR: Port number must be in range 1-65535. Supplied: " + portNumber + ".");
	        	System.exit(1);
	        }
				
			Queue<Thread> myWorkerThreads = new LinkedList<Thread>();
			ServerSocket serverSocket = null;
	        try {
	            serverSocket = new ServerSocket(portNumber);
	        } catch (IOException e) {
	        	printErrorToConsole("0x001 Error encountered.");
	            printErrorToConsole("  Description: Server could not listen on port: " + portNumber + ".");
	            System.exit(1);
	        }
	        
	        Socket clientSocket = null;
	        boolean alive = true;
	        int reconnect_interval = 5; //time to wait after I/O exception encountered
	        
	        addSpaceArrivedFromParent = false;
	        countArrivedFromChild = new boolean[numImmediateChildren];
	        boolean[] allTrue = new boolean[numImmediateChildren];
	        for(int i = 0; i < countArrivedFromChild.length; i++)
			{
				countArrivedFromChild[i] = false;
				allTrue[i] = true;
			}
	        haveSentAddSpaceMessages = false;
	        haveSentCountToParent = false;
	        printToConsole("Listening for connections on port: " + portNumber + ". (id=" + idHash + ")");
	        while(alive)
	        {
		        try {
		        	//state = ServerState.ready; //enter ready state once listening
		        	
		        	//must implement some way of closing the server from the console
		        	serverSocket.setSoTimeout(1000); 
		        	boolean connected = false;
		        	while(!connected)
		        	{
		        		try{
		        			clientSocket = serverSocket.accept();
		        			connected = true;
		        			ClientWorker w = new ClientWorker(this, clientSocket, serverName, portNumber);
		                    Thread t = new Thread(w);
		                    t.start();
		                    myWorkerThreads.add(t);
		                    //---start intermediate server work while listening
		        		} catch (SocketTimeoutException ste) {
		        			//check some signal that will allow threads
		        			//to check if they should be closed
		        			if(server.signalToKillAllServerThreads)
		        			{
	        					alive = false;
	        					break;
		        			}
		                    if(isRoot)
		                    {
		                    	if(Arrays.equals(countArrivedFromChild,allTrue) && !haveSentAddSpaceMessages)
								{
		                    		//if we are root, then we can send our ADD-SPACE messages
		                    		addressStartHex32 = rangeStart = "00000000000000000000000000000000";
									addressEndHex32 = rangeEnd = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
									  //divide up and send out to children
									int childCountsTotal = 0;
									for(int i=0; i<childCounts.length; i++)
										childCountsTotal += childCounts[i];
									  keywordHashBoundaries = Helpers.getHashBoundaries(addressStartHex32, addressEndHex32, childCountsTotal+1);
									  //printToConsole("All boundaries:" + Arrays.deepToString(keywordHashBoundaries));
									  myRangeStart = keywordHashBoundaries[0][0];
									  myRangeEnd = keywordHashBoundaries[0][1];
									  childKeywordHashBoundaries = new String[children.length][2];
									  //printToConsole("Root range: " + myRangeStart + " " + myRangeEnd);
									  int childCount;
									  int pointer = 1;
									  for(int i=0; i < children.length; i++)
									  {
										  childCount = childCounts[i];
										  String childRangeStart = keywordHashBoundaries[pointer][0];
										  pointer += childCount;
										  String childRangeEnd = keywordHashBoundaries[pointer-1][1];
										  childKeywordHashBoundaries[i][0] = childRangeStart;
										  childKeywordHashBoundaries[i][1] = childRangeEnd;
										  String message = "add-space 0x0B 4 " + childRangeStart + " " +
										  					childRangeEnd + " 32 " + idHash;
										  //add-space 0x0B 4 [range start] [range end] 32 idHash
										  try {
											Helpers.sendMessage(children[i], message);
											} catch (UnknownHostException e) {
												printErrorToConsole("ERROR: Could not resolve hostname for server: " + Arrays.toString(children[i-1]));
											} catch (IOException e) {
												printErrorToConsole("ERROR: I/O Exception sending ADD-SPACE message to: " + Arrays.toString(children[i-1]));
											}
										  
									  }
									//make sure we set flag so we do not send more than once
									haveSentAddSpaceMessages = true;
								}
		                    	//then we just listen for connections and reply as needed
		                    }
		                    
		                    
							if(isParent && !isRoot)
		                    {
								//printToConsole(Arrays.toString(countArrivedFromChild) + Arrays.toString(allTrue));
//								try {
//									Thread.sleep(5000);
//								} catch (InterruptedException e) {
//									
//									e.printStackTrace();
//								}
//								
		                    	if(Arrays.equals(countArrivedFromChild, allTrue) && !haveSentCountToParent)
								{
		                    		//printToConsole("I am a parent and all my counts have arrived.");
		                    		sendCountToParent();
		                    		haveSentCountToParent = true;
		                    		startHelloTimer();
								}
		                    	if(addSpaceArrivedFromParent && !haveSentAddSpaceMessages)
		                    	{
		                    		//then we can send out our addspace messages to the children
		                    		addressStartHex32 = rangeStart;
									addressEndHex32 = rangeEnd;
									  //divide up and send out to children
									int childCountsTotal = 0;
									for(int i=0; i<childCounts.length; i++)
										childCountsTotal += childCounts[i];
									  keywordHashBoundaries = Helpers.getHashBoundaries(addressStartHex32, addressEndHex32, childCountsTotal+1);
									  //printToConsole(Arrays.deepToString(keywordHashBoundaries));
									  myRangeStart = keywordHashBoundaries[0][0];
									  myRangeEnd = keywordHashBoundaries[0][1];
									  childKeywordHashBoundaries = new String[children.length][2];
									  //printToConsole("My range: " + myRangeStart + " " + myRangeEnd);
									  int childCount;
									  int pointer = 1;
									  for(int i=0; i < children.length; i++)
									  {
										  childCount = childCounts[i];
										  String childRangeStart = keywordHashBoundaries[pointer][0];
										  pointer += childCount;
										  String childRangeEnd = keywordHashBoundaries[pointer-1][1];
										  childKeywordHashBoundaries[i][0] = childRangeStart;
										  childKeywordHashBoundaries[i][1] = childRangeEnd;
										  String message = "add-space 0x0B 4 " + childRangeStart + " " +
										  					childRangeEnd + " 32 " + idHash;
										  //add-space 0x0B 4 [range start] [range end] 32 idHash
										  try {
											Helpers.sendMessage(children[i], message);
											} catch (UnknownHostException e) {
												printErrorToConsole("ERROR: Could not resolve hostname for server: " + Arrays.toString(children[i-1]));
											} catch (IOException e) {
												printErrorToConsole("ERROR: I/O Exception sending ADD-SPACE message to: " + Arrays.toString(children[i-1]));
											}
										  
									  }
		                    		haveSentAddSpaceMessages = true;
		                    	}
		                    }
		                    
		                    if(isChild && !isParent)
		                    {
		                    	if(!haveSentCountToParent)
		                    	{
		                    		sendCountToParent();
			                    	haveSentCountToParent = true;
			                    	startHelloTimer();
		                    	}
		                    }
		                    
		                    //if((isChild && !isParent && haveSentCountToParent) || (isParent && haveSentAddSpaceMessages))
		                    if(addSpaceArrivedFromParent)
		                    {
		                    	if(finishedHelloTimer())
		                    	{
		                    		//printToConsole("sending periodic hello");
		                    		sendCountToParent();
		                    		startHelloTimer();
		                    	}
		                    }
		                    
		                    
		                    //---end intermediate server work while listening
		        			}
		        		
		        		
		        	}
		        	if(!alive)
		        		break;
		            printToConsole("Received connection on port: " + portNumber + ".");
		        } catch (IOException e) {
		        	printErrorToConsole("0x004 Error encountered.");
		            printErrorToConsole("  Description: I/O exception: Server connection accept failed.");
		            int wait_time = reconnect_interval;
		            System.out.print("  Opening new socket in " + reconnect_interval);
		            while(wait_time > 0)
		            {
		            	wait_time--;
		            	try {
							Thread.sleep(1000);
						} catch (InterruptedException e1) {
							
							e1.printStackTrace();
						}
		            	//System.out.print(".." + wait_time);
		            }
		            //System.out.println();
		            continue; //exit current iteration and try again
		        }
		        
		        //state = ServerState.connected; //enter connected state once client successfully connects
	        }
	        
	        //printToConsole("Waiting for all ClientWorker threads to close...");
	        while(myWorkerThreads.size() > 0)
	        {
	        	Thread t = myWorkerThreads.remove();
	        	if(t != null)
	        	{
	        		while(t.isAlive())
	        		{ //sit here and wait 
	        		}
	        	}
	        }
	        printToConsole("All ClientWorker threads closed.");
			  
	  }
	  
	  
	  private void startHelloTimer()
	  {
		  helloTimerEnd = System.currentTimeMillis() + helloTimerPeriod;
	  }
	  
	  private boolean finishedHelloTimer()
	  {
		  return (System.currentTimeMillis() > helloTimerEnd);
	  }

	private void sendCountToParent() {
		//we need to send our counts in the HELLO message
		  int childCountsTotal = 0;
		for(int i=0; i<childCounts.length; i++)
			childCountsTotal += childCounts[i];
      	String message;
      	if(isChild && !isParent)
      		message = "hello 0x0B 5 1 " +
								idHash + " " + ipAddress + " " + portNumber + " true";
      	else
      	{
      		message = "hello 0x0B 5 " + (childCountsTotal+1) + " " +
      				idHash + " " + ipAddress + " " + portNumber + " true";
      	}
      	// hello 0x0B 5 [numChildren+1] [my ID hash] [my unicast ip] [my port] true
      	
      	try {
      		if((isParent && !haveSentAddSpaceMessages) || (isChild && !haveSentCountToParent))
          	{
          		Helpers.sendMessage(parent, message); //do not block for rep-hello
          	}
      		else
      		{
				String reply = Helpers.sendMessageRcvReply(parent, message);
				ClientWorker w = new ClientWorker(this, null, serverName, portNumber);
				w.processMessage(reply);
      		}
		} catch (UnknownHostException e) {
			
			e.printStackTrace();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
      	
	}

	protected void finalize(){
		//Objects created in run method are finalized when
		//program terminates and thread exits
//		  System.out.println("Closing connections");
//		     try{
//		    	 client.close();
//		    } catch (IOException e) {
//		        System.out.println("Could not close socket");
//		        System.exit(-1);
//		    }
		  }
	}

class ClientWorker implements Runnable {
	  private ServerWorker serverWorker;
	  private Socket client;
	  private String serverName;
	  private int portNumber;
	  private PrintWriter out;

	//Constructor
	  ClientWorker(ServerWorker serverWorker, Socket client, String serverName, int portNumber) {
	    this.serverWorker = serverWorker;
		this.client = client;
	    this.serverName = serverName;
	    this.portNumber = portNumber;
	  }
	  
	  public void printToConsole(String message)
	  {
		  System.out.println("  " + serverName + ">CommThread(" + getThreadID() + "): " + message);
		  System.out.flush();
	  }
	  
	  public void printErrorToConsole(String message)
	  {
		  System.err.println("  " + serverName + ">CommThread(" + getThreadID() + "): " + message);
	  }
	  
	  public void printIncomingMessage(String message)
	  {
		  if(!message.startsWith("bye"))
			  printToConsole("<-- in  : " + message);
	  }
	  
	  public void printOutgoingMessage(String message)
	  {
		  if(!message.startsWith("bye"))
			  printToConsole("out --> : " + message);
	  }
	  
	  public long getThreadID()
	  {
		  return Thread.currentThread().getId();
	  }

	  public void run(){
	    String inline;
	    BufferedReader in = null;
	    out = null;
	    //printToConsole("Started");
	    try{
	      in = new BufferedReader(new 
	        InputStreamReader(client.getInputStream()));
	      out = new 
	        PrintWriter(client.getOutputStream(), true);
	    } catch (IOException e) {
	    	printErrorToConsole("ERROR: could not get I/O streams");
	    	System.exit(-1);
	    }

	    boolean toldToKill = false;
	    while(true){
	      try{
	    	  while(!in.ready())
	    	  {
	    		  if(server.signalToKillAllServerThreads)
	    		  {
	    			  toldToKill = true;
	    			  break;
	    		  }  
	    		  try {
	  				Thread.sleep(500);
		  			} catch (InterruptedException e) {
		  				
		  				e.printStackTrace();
		  			}
	    	  }
	    	  if(toldToKill)
	    		  break;
	        inline = in.readLine();
	        printIncomingMessage(inline);
	        String response = processMessage(inline);
	        if(response != null) //Send data back to client
	        {
		        printOutgoingMessage(response);
		        out.println(response);
		        if(response.toUpperCase().startsWith("bye"))
		        	break;
	        }
	        
	        
	//Append data to text area
	       }catch (IOException e) {
	    	   printErrorToConsole("Read failed");
	    	   System.exit(-1);
	       }
	    }
	    
	     try{
	    	 client.close();
	    } catch (IOException e) {
	    	printErrorToConsole("ERROR: Closing socket.");
	        System.exit(-1);
	    }
	    System.out.flush();
	    //printToConsole("Closed.");
	    
	  }
	  
	  public String processMessage(String inline) 
	  {
		  if(inline == null || inline.trim().length() <= 0)
			  return null;
		  String originalMessage = inline;
		  try
		  {
		  
		  //inline = inline.toUpperCase();
		  String[] fields = Helpers.extractFields(inline, " ");
		  
		  //TODO: add field format/length error checking to all these commands
		  if(inline.toUpperCase().startsWith("BYE"))
		  {
			  return originalMessage;
		  }
		  else if(inline.toUpperCase().startsWith("HELLO"))
		  {
			// hello 0x0B 5 [numChildren+1] [my ID hash] [my unicast ip] [my port] true
			  int numChildren = 0;
			  numChildren = Integer.parseInt(fields[Helpers.HELLO_NUMCHILDREN]);
			  String senderIDHash = fields[Helpers.HELLO_IDHASH];
			  int childIndexOfSender = getChildIndexFromIDHash(senderIDHash);
			  if(childIndexOfSender >= 0)
			  {
				  serverWorker.childCounts[childIndexOfSender] = numChildren;
				  serverWorker.countArrivedFromChild[childIndexOfSender] = true;
			  }
			  
			  //wait for add-space messages to send
//			  int timeoutPeriod = 5000;
//			  int checkInterval = 500;
//			  int stopWatch = 0;
//			  while(!serverWorker.haveSentAddSpaceMessages && stopWatch <= timeoutPeriod)
//			  {
//				  stopWatch += checkInterval;
//				  try {
//						Thread.sleep(checkInterval);
//					} catch (InterruptedException e) {
//						
//						e.printStackTrace();
//					}
//			  }
			  
			  if(serverWorker.haveSentAddSpaceMessages)
			  {
				  String reply = "rep-hello 0x0B 3 " + serverWorker.idHash + 
					" " + serverWorker.rangeStart + " " + serverWorker.rangeEnd;
				  //[rep-hello] [message direction] [3] [ID hash] [my address start] [my address end]
				  return reply;
			  }
			  
			  
			  
			  
			                                                  
		  }
		  else if(inline.toUpperCase().startsWith("ADD-SPACE "))
		  {
			  serverWorker.rangeStart = fields[Helpers.ADDSPACE_RANGESTART];
			  serverWorker.rangeEnd = fields[Helpers.ADDSPACE_RANGEEND];
			  if(serverWorker.isChild && !serverWorker.isParent)
			  {
				  serverWorker.myRangeStart = serverWorker.rangeStart;
				  serverWorker.myRangeEnd = serverWorker.rangeEnd;
			  }
			  serverWorker.addSpaceArrivedFromParent = true;
		  }
		  else if(inline.startsWith("REP-HELLO "))
		  {
			  if(serverWorker.addSpaceArrivedFromParent)
			  {
				  if(serverWorker.parentRangeStart == null || 
						  !serverWorker.parentRangeStart.equals(fields[Helpers.REPHELLO_RANGESTART]))
					  serverWorker.parentRangeStart = fields[Helpers.REPHELLO_RANGESTART];
				  if(serverWorker.parentRangeEnd == null || 
						  !serverWorker.parentRangeEnd.equals(fields[Helpers.REPHELLO_RANGEEND]))
					  serverWorker.parentRangeEnd = fields[Helpers.REPHELLO_RANGEEND];
			  }
		  }
		  else if(inline.toUpperCase().startsWith("CHECK "))
		  {
			  //[check] [0x01] [2] [char-set] [keyword]
			  
			  //pull keyword out
			  String keyword = fields[Helpers.CHECK_KEYWORD];
			  //hash it
			  String keywordHash = Helpers.getMD5Hash(keyword);
			  //check hash against db
			  
			  if(isInServerHashRange(keywordHash))
			  {
				  printToConsole(keyword + " = " + keywordHash + " found in my range");
				  if(serverWorker.db.recordExistsMD5(keywordHash))
					  return "check-response 0x03 1 false"; //taken, can not register this
				  else
					  return "check-response 0x03 1 true"; //not taken, can register this
			  }
			  else
			  {
				  //need to check other URS's before we can honestly say if
				  //it is available
				  
				  if(serverWorker.isParent)
				  {
					//if i am the root
					  	//check to see if md5 hash is in any of my childrens ranges
					  	//if it is, forward on to that child, wait for response
					  		//return response
					  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
					  if(childIndexMatch != -1)
					  {
						  try {
							return Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], originalMessage);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
					  }
					  else
					  {
						  if(serverWorker.isRoot) //this should never get hit, it always should find it
							  printErrorToConsole("Root could not find keyword in self or children.");
						  else
						  {
							//if it is not
						  		//send CHECK message to parent, wait for response
					  			//return response
							  try {
									return Helpers.sendMessageRcvReply(serverWorker.parent, originalMessage);
								} catch (UnknownHostException e) {
									
									e.printStackTrace();
								} catch (IOException e) {
									
									e.printStackTrace();
								}
						  }
					  }
					  
				  }
				  else if(serverWorker.isChild && !serverWorker.isParent)
				  {
					//if i am a child, and not a parent
					  	//send CHECK message to parent, wait for response
				  		//return response
					  try {
						return Helpers.sendMessageRcvReply(serverWorker.parent, originalMessage);
					} catch (UnknownHostException e) {
						
						e.printStackTrace();
					} catch (IOException e) {
						
						e.printStackTrace();
					}
				  }
				  else if(serverWorker.isParent && !serverWorker.isRoot)
				  {
					//if i am a parent
					  	//check to see if md5 hash is in any of my childrens ranges
					  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
					  if(childIndexMatch != -1)
					  {
						  try {
							return Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], originalMessage);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
					  }
					  	
				  }
				  
			  }
		  }
		  else if(inline.toUpperCase().startsWith("REGISTER "))
		  {
			//pull keyword out
			  String keyword = fields[Helpers.REGISTER_KEYWORD];
			  //hash it
			  String keywordHash = Helpers.getMD5Hash(keyword);
			  //check hash against db
			
			  //check if keyword hash is in range of this node
			  if(isInServerHashRange(keywordHash))
			  {
				  //check if it exists in the db
				  //printToConsole(keyword + " = " + keywordHash + " found in my range");
				  if(serverWorker.db.recordExistsMD5(keywordHash))
					  return "register-status 0x03 1 false"; //taken, can not register this
				  else
				  {
					//if it is: continue on with inserting into db
					  
					  int start =  Helpers.REGISTER_KEYWORD;
					  int end = fields.length;
					  String[] subset = new String[end-start];
					  int subseti = 0;
					  for(int i = 0; i < fields.length; i++)
					  {
						 if(i >= start && i <= end)
						 {
							 subset[subseti] = fields[i];
							 subseti++;
						 }
							 
					  }
					  String record = keywordHash + " " + 
					  Helpers.arrayToDelimitedList(subset, " ");
					  
		      			serverWorker.db.createRecord(record);
		      			//register 0x01 7 ASCII gators 123.456.789.100 1234 100.789.456.123 5678 192.193.194.195
					  return "register-status 0x03 1 true"; //not taken, can register this
				  }
			  }
			  else//if it is not
			  {
				  //determine where to send it
  					//check children ranges first, then parent
				  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
				  if(childIndexMatch != -1)
				  {
					  try {
						return Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], originalMessage);
					} catch (UnknownHostException e) {
						
						e.printStackTrace();
					} catch (IOException e) {
						
						e.printStackTrace();
					}
				  }
				  else //then check parent
				  {
					  try {
							return Helpers.sendMessageRcvReply(serverWorker.parent, originalMessage);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
				  }
					
					
			  }
		  }
		  else if(inline.toUpperCase().startsWith("MSD-PROBE "))
		  {
			//pull keyword out
			  String keyword = fields[Helpers.MSDPROBE_KEYWORD];
			  //hash it
			  String keywordHash = Helpers.getMD5Hash(keyword);
			  int hopCount = Integer.parseInt(fields[Helpers.MSDPROBE_HOPCOUNT]);
			  hopCount++;
//			  String msdIP = fields[Helpers.MSDPROBE_MSDIP];
//			  String msdPort = fields[Helpers.MSDPROBE_MSDPORT];
			  
//			  [msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP]
//			  [msd port] [hop count] [inversion flag][\n]
			  
//			  [msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
//			  [hop count] [inversion flag][\n]
			  
			  if(isInServerHashRange(keywordHash))
			  {
				  return "msd-probe-reply 0x0B 6 ASCII " + keyword + " "
					  + serverWorker.ipAddress + " " + serverWorker.portNumber + " "
					  + hopCount + " false";
			  }
			  else//if it is not
			  {
				  fields[Helpers.MSDPROBE_HOPCOUNT] = Integer.toString(hopCount);
				  String msdProbeMsgToFwd = Helpers.arrayToDelimitedList(fields, " ");
				  //determine where to send it
  					//check children ranges first, then parent
				  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
				  if(childIndexMatch != -1)
				  {
					  try {
						return Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], msdProbeMsgToFwd);
					} catch (UnknownHostException e) {
						
						e.printStackTrace();
					} catch (IOException e) {
						
						e.printStackTrace();
					}
				  }
				  else //then check parent
				  {
					  try {
							return Helpers.sendMessageRcvReply(serverWorker.parent, msdProbeMsgToFwd);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
				  }
					
					
			  }
			  
		  }
		  else if(inline.toUpperCase().startsWith("SEARCH "))
		  {
			//pull keyword out
			  //[search] [0x0A] [3] [char-set] [parameter] [client port][\n]
			  String parameter = fields[Helpers.SEARCH_PARAMETER];
			  String justKeywords = parameter.split("%")[0];
			  String[] keyWordSets = Helpers.extractFields(justKeywords, "&");
			  String[][] allKeywords = new String[keyWordSets.length][];
			  for(int i=0; i<keyWordSets.length; i++)
			  {
				  allKeywords[i] = Helpers.extractFields(keyWordSets[i], ":");
			  }
			  ArrayList<String> uniqueList = new ArrayList<String>();
			  for(int i=0; i<allKeywords.length; i++)
			  {
				  for(int j=0; j<allKeywords[i].length; j++)
				  {
					  String currentKeyword = allKeywords[i][j];
					  if(!uniqueList.contains(currentKeyword))
					  {
						  uniqueList.add(currentKeyword);
						  String currentKeywordHash = Helpers.getMD5Hash(currentKeyword);
						  if(isInServerHashRange(currentKeywordHash))
						  {
							  //if keyword is local
							  //send back using search-response
							  out.println("search-response 0x07 11 ASCII global "
							  		+ currentKeyword + " " + serverName + " "
							  		+ serverWorker.idHash + " inf lat long nettype streamtype 1");
							  
							  //search-response 0x07 11 ASCII global *keyword *serverName
							  //*idhash inf lat long nettype streamtype hopcount
							  
						  }
						  else//if it is far away
						  {
							  //send out an MSD-PROBE
		//					  [msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP]
		//					  [msd port] [hop count] [inversion flag][\n]
							  String msdProbeSend = "msd-probe 0x0B 6 ASCII " + currentKeyword + " "
							  					  + serverWorker.ipAddress + " " + serverWorker.portNumber + " "
							  					  + "1 false";
							  String msdProbeReply = null;
							  
							  
							  //determine where to send it
			  					//check children ranges first, then parent
							  int childIndexMatch = childIndexHashRangeMatch(currentKeywordHash);
							  if(childIndexMatch != -1)
							  {
								  try {
									  msdProbeReply = Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], msdProbeSend);
									
								} catch (UnknownHostException e) {
									
									e.printStackTrace();
								} catch (IOException e) {
									
									e.printStackTrace();
								}
							  }
							  else //then check parent
							  {
								  try {
									  msdProbeReply = Helpers.sendMessageRcvReply(serverWorker.parent, msdProbeSend);
									} catch (UnknownHostException e) {
										
										e.printStackTrace();
									} catch (IOException e) {
										
										e.printStackTrace();
									}
							  }
							  //wait for response
	//						  [msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
	//						  [hop count] [inversion flag][\n]
							  //parse response
							  String[] msdProbeFields = Helpers.extractFields(msdProbeReply, " ");
							  int hopCount = Integer.parseInt(msdProbeFields[Helpers.MSDPROBEREPLY_HOPCOUNT]);
							  hopCount++;
							  String redirectMessageToReturn = "redirect 0x07 5 ASCII " + currentKeyword + " "
							  								 + msdProbeFields[Helpers.MSDPROBEREPLY_MSDIP] + " "
							  								 + msdProbeFields[Helpers.MSDPROBEREPLY_MSDPORT] + " "
							  								 + hopCount + " false";
							  out.println(redirectMessageToReturn);
						  }
					  }
				  }
				  //send a "tx-end dext"
				  //[tx-end] [message direction] [3] [char-set] [keyword] [tag][\n]
				  out.println("tx-end 0x03 3 ASCII " + keyWordSets[i] + " dext");
			  }
		  }
		  else if(inline.toUpperCase().startsWith("EXT-SEARCH "))
		  {
			  //[ext-search] [0x0A] [5] [char-set] [parameter] [IP] [port] [inversion flag][\n]
			  //ext-search 0x0A 5 ASCII key1:key2&key3:key4 IPofClientWhoSent portOfClientWhoSent false
			  
			  //[ext-search-response] [0x08] [11] [char-set] [global] [keyword]
			  // [domain URI] [URS Identifier] [expiration time] [Lat] [Long] [network
			  // type] [stream type] [hop count][\n]
			  
			  //[ext-search-invalid] [0x08] [2] [char-set] [keyword][\n]
			  
			  //for each keyword in parameter
			  	//check if keyword is in this server's hash range
			  		//if it is:
			  			//find the matching record and send back info in
			  			//an ext-search-response message
			  		//If it is not:
			  			//send a [ext-search-invalid] message back.
			  
			//pull keyword out
			  String parameter = fields[Helpers.EXTSEARCH_PARAMETER];
			  String justKeywords = parameter.split("%")[0];
			  String[] keyWordSets = Helpers.extractFields(justKeywords, "&");
			  String[][] allKeywords = new String[keyWordSets.length][];
			  for(int i=0; i<keyWordSets.length; i++)
			  {
				  allKeywords[i] = Helpers.extractFields(keyWordSets[i], ":");
			  }
			  ArrayList<String> uniqueList = new ArrayList<String>();
			  for(int i=0; i<allKeywords.length; i++)//for each keyword in parameter
			  {
				  for(int j=0; j<allKeywords[i].length; j++)
				  {
					  String currentKeyword = allKeywords[i][j];
					  if(!uniqueList.contains(currentKeyword))
					  {
						  uniqueList.add(currentKeyword);
						  String currentKeywordHash = Helpers.getMD5Hash(currentKeyword);
						  if(isInServerHashRange(currentKeywordHash))//check if keyword is in this server's hash range
						  {
							  //if(serverWorker.db.recordExistsMD5(currentKeywordHash))
							  //{
								  //find the matching record and send back info in
						  		  //an ext-search-response message
								  //String[] record = serverWorker.db.getRecordMD5(currentKeywordHash);
								  //[ext-search-response] [0x08] [11] [char-set] [global] [keyword]
								  // [domain URI] [URS Identifier] [expiration time] [Lat] [Long] [network
								  // type] [stream type] [hop count][\n]
								  out.println("ext-search-response 0x08 11 ASCII global " + currentKeyword + " "
										  	+  serverName + " "	+ serverWorker.idHash + " inf lat long nettype" +
										  	  " streamtype 1");
								  
							  //}
						  }
						  else//if it is far away
						  {
							//send a [ext-search-invalid] message back.
							//[ext-search-invalid] [0x08] [2] [char-set] [keyword][\n]
							  out.println("ext-search-invalid 0x08 2 ASCII " + currentKeyword);
						  }
					  }
				  }
				  out.println("tx-end 0x03 3 ASCII " + keyWordSets[i] + " dext");
			  }
		  }
		  else if(inline.toUpperCase().startsWith("INVALIDATE "))
		  {
//			  [invalidate] [0x0A] [7] [char-set] [keyword] [cached IP] [cached
//              port] [IP] [port] [inversion flag][\n]
			  
			//pull keyword out
			  String keyword = fields[Helpers.INVALIDATE_KEYWORD];
			  //hash it
			  String keywordHash = Helpers.getMD5Hash(keyword);
			  
			  if(isInServerHashRange(keywordHash))
			  {
				  out.println("redirect 0x07 5 ASCII " + keyword + " "
					 + serverWorker.ipAddress + " "
					 + serverWorker.portNumber + " 1 false");
			  }
			  else
			  {
				  
				//send out an MSD-PROBE
	//					  [msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP]
	//					  [msd port] [hop count] [inversion flag][\n]
				  String msdProbeSend = "msd-probe 0x0B 6 ASCII " + keyword + " "
				  					  + serverWorker.ipAddress + " " + serverWorker.portNumber + " "
				  					  + "1 false";
				  String msdProbeReply = null;
				  
				  
				  //determine where to send it
					//check children ranges first, then parent
				  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
				  if(childIndexMatch != -1)
				  {
					  try {
						  msdProbeReply = Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], msdProbeSend);
						
					} catch (UnknownHostException e) {
						
						e.printStackTrace();
					} catch (IOException e) {
						
						e.printStackTrace();
					}
				  }
				  else //then check parent
				  {
					  try {
						  msdProbeReply = Helpers.sendMessageRcvReply(serverWorker.parent, msdProbeSend);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
				  }
				  //wait for response
	//						  [msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
	//						  [hop count] [inversion flag][\n]
				  //parse response
				  String[] msdProbeFields = Helpers.extractFields(msdProbeReply, " ");
				  int hopCount = Integer.parseInt(msdProbeFields[Helpers.MSDPROBEREPLY_HOPCOUNT]);
				  hopCount++;
				  String redirectMessageToReturn = "redirect 0x07 5 ASCII " + keyword + " "
				  								 + msdProbeFields[Helpers.MSDPROBEREPLY_MSDIP] + " "
				  								 + msdProbeFields[Helpers.MSDPROBEREPLY_MSDPORT] + " "
				  								 + hopCount + " false";
				  out.println(redirectMessageToReturn);
			  }
		  }
		  else if(inline.toUpperCase().startsWith("QUERY "))
		  {
			  //[query] [0x01] [2] [char-set] [URS-Identifier][\n]
			  
			  //[query-response] [0x03] [1] [null][\n]
			  
			  //[query-response] [0x03] [17] [char-set] [channel IP] [channel port]
//			   [Geographic CN] [Lat] [Long] [unicast IP] [unicast port] [channel
//			   scope] [URS-Identifier] [expiration time] [network type] [source IP]
//			   [stream type] [preferred application] [CLI argument] [mime-type][\n]
			  
			//pull keyword out
			  String keyword = fields[Helpers.QUERY_KEYWORD];
			  //hash it
			  String keywordHash = Helpers.getMD5Hash(keyword);
			  //check hash against db
			
			  //check if keyword hash is in range of this node
			  if(isInServerHashRange(keywordHash))
			  {
				  //check if it exists in the db
				  //printToConsole(keyword + " = " + keywordHash + " found in my range");
				  if(serverWorker.db.recordExistsMD5(keywordHash))
				  {
					  String[] record = serverWorker.db.getRecordMD5(keywordHash);
					  record[0] = "ASCII"; //don't need MD5 hash, but do need char-set
					  int numFields = record.length;
					  return "query-response 0x03 " + numFields + " " + Helpers.arrayToDelimitedList(record, " ");
				  }
				  else
				  {
					  return "query-response 0x03 1 null";
				  }
			  }
			  else//if it is not
			  {
				  //determine where to send it
  					//check children ranges first, then parent
				  int childIndexMatch = childIndexHashRangeMatch(keywordHash);
				  if(childIndexMatch != -1)
				  {
					  try {
						return Helpers.sendMessageRcvReply(serverWorker.children[childIndexMatch], originalMessage);
					} catch (UnknownHostException e) {
						
						e.printStackTrace();
					} catch (IOException e) {
						
						e.printStackTrace();
					}
				  }
				  else //then check parent
				  {
					  try {
							return Helpers.sendMessageRcvReply(serverWorker.parent, originalMessage);
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (IOException e) {
							
							e.printStackTrace();
						}
				  }
					
					
			  }
		  }
		  else
		  {
			  return "error 0x03 1 false NotValidMessage";
		  }
		  }
		  catch(Exception e)
		  {
			  return "error 0x03 1 false NotValidMessage";
		  }
		  return null;
	  }
					  	
					  
					  	

			  
//			  keyword(:keyword)*(&keyword(:keyword)*)*%local-scope:global-scope
//			   (%lat:long%radius)?

		  
		  

		  
			//shall respond to:
			//*CHECK		*CHECK-RESPONSE
			//*REGISTER		*REGISTER-STATUS
			//*SEARCH		*SEARCH-RESPONSE or *REDIRECT
							//when over, *TX-END
			//*EXT-SEARCH	*EXT-SEARCH-RESPONSE or *EXT-SEARCH-INVALID
							//when over, *TX-END
			//INVALIDATE	REDIRECT
			//BYE			BYE
							//server shall return to its ready state
		  
		 
		
	  

	private int childIndexHashRangeMatch(String keywordHash) {
		BigInteger keywordHashBI = new BigInteger(keywordHash,16);
		if(serverWorker.childKeywordHashBoundaries == null)
			return -1;
		int index = 0;
		for(String[] childHashBoundary : serverWorker.childKeywordHashBoundaries)
		{
			BigInteger myRangeStartBI = new BigInteger(childHashBoundary[0],16);
			BigInteger myRangeEndBI = new BigInteger(childHashBoundary[1],16);
			int comp1 = keywordHashBI.compareTo(myRangeStartBI);
			int comp2 = keywordHashBI.compareTo(myRangeEndBI);
			boolean gteStart = comp1==0 || comp1==1;
			boolean lteEnd = comp2==-1 || comp2==0;
			if (gteStart && lteEnd)
				return index;
			index++;
		}
		return -1;
		
	}

	private boolean isInServerHashRange(String keywordHash) {
		//printToConsole(keywordHash + "," + serverWorker.myRangeStart + "," + serverWorker.myRangeEnd);
		BigInteger keywordHashBI = new BigInteger(keywordHash,16);
		BigInteger myRangeStartBI = new BigInteger(serverWorker.myRangeStart,16);
		BigInteger myRangeEndBI = new BigInteger(serverWorker.myRangeEnd,16);
		//printToConsole(keywordHashBI + "," + myRangeStartBI + "," + myRangeEndBI);
		int comp1 = keywordHashBI.compareTo(myRangeStartBI);
		int comp2 = keywordHashBI.compareTo(myRangeEndBI);
		//printToConsole(comp1 + "," + comp2);
		boolean gteStart = comp1==0 || comp1==1;
		boolean lteEnd = comp2==-1 || comp2==0;
		return (gteStart && lteEnd);
	}

	private int getChildIndexFromIDHash(String senderIDHash) {
		
		for(int i = 0; i < serverWorker.childrenIDHash.length; i++)
		  {
			  if(serverWorker.childrenIDHash[i].equals(senderIDHash))
				  return i;
		  }
		return -1;
	}

	protected void finalize(){
		//Objects created in run method are finalized when
		//program terminates and thread exits
		  
		  }
	}